package cn.sylinx.horm.proxy.command.invoker;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cn.sylinx.horm.config.OrmConfigHolder;
import cn.sylinx.horm.core.common.Page;
import cn.sylinx.horm.core.common.Record;
import cn.sylinx.horm.exception.HORMException;
import cn.sylinx.horm.proxy.command.CommandMethodMetadata;
import cn.sylinx.horm.proxy.command.annotation.Resource;
import cn.sylinx.horm.proxy.command.annotation.ResourceType;
import cn.sylinx.horm.proxy.command.interceptor.CommandInvokerProxy;

public enum CommandInvokerManager {
    ;
    private static Set<Class<?>> primaryOrMappedWrapTypeOrFrequentType = new HashSet<>(8);

    static {
        primaryOrMappedWrapTypeOrFrequentType.add(Boolean.class);
        primaryOrMappedWrapTypeOrFrequentType.add(boolean.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Byte.class);
        primaryOrMappedWrapTypeOrFrequentType.add(byte.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Character.class);
        primaryOrMappedWrapTypeOrFrequentType.add(char.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Short.class);
        primaryOrMappedWrapTypeOrFrequentType.add(short.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Integer.class);
        primaryOrMappedWrapTypeOrFrequentType.add(int.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Long.class);
        primaryOrMappedWrapTypeOrFrequentType.add(long.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Float.class);
        primaryOrMappedWrapTypeOrFrequentType.add(float.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Double.class);
        primaryOrMappedWrapTypeOrFrequentType.add(double.class);
        primaryOrMappedWrapTypeOrFrequentType.add(String.class);
        primaryOrMappedWrapTypeOrFrequentType.add(BigInteger.class);
        primaryOrMappedWrapTypeOrFrequentType.add(BigDecimal.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Date.class);
    }

    private static final Map<String, CommandInvoker> commandInvokerMaps = new HashMap<>();
    private static final Map<String, CommandInvoker> cachedCommandInvokerMaps = new HashMap<>();

    static {
        // key rules: "type."
        String updateKey = "UPDATE.";
        commandInvokerMaps.put(updateKey, maybeProxyed(new UpdateCommandInvoker()));
        String insertKey = "INSERT.";
        commandInvokerMaps.put(insertKey, maybeProxyed(new InsertCommandInvoker()));
        String executeKey = "EXECUTE.";
        commandInvokerMaps.put(executeKey, maybeProxyed(new ExecuteCommandInvoker()));
        String queryKey1 = "QUERY.LIST.BEAN";
        commandInvokerMaps.put(queryKey1, maybeProxyed(new BeanListCommandInvoker()));
        String queryKey2 = "QUERY.LIST.RECORD";
        commandInvokerMaps.put(queryKey2, maybeProxyed(new RecordListCommandInvoker()));
        String queryKey3 = "QUERY.LIST.OBJ";
        commandInvokerMaps.put(queryKey3, maybeProxyed(new ObjectListCommandInvoker()));
        String queryKey4 = "QUERY.PAGE.BEAN";
        commandInvokerMaps.put(queryKey4, maybeProxyed(new BeanPageCommandInvoker()));
        String queryKey5 = "QUERY.PAGE.RECORD";
        commandInvokerMaps.put(queryKey5, maybeProxyed(new RecordPageCommandInvoker()));
        String queryKey6 = "QUERY.ONE.BEAN";
        commandInvokerMaps.put(queryKey6, maybeProxyed(new BeanQueryOneCommandInvoker()));
        String queryKey7 = "QUERY.ONE.RECORD";
        commandInvokerMaps.put(queryKey7, maybeProxyed(new RecordQueryOneCommandInvoker()));
        String queryKey8 = "QUERY.ONE.OBJ";
        commandInvokerMaps.put(queryKey8, maybeProxyed(new ObjectQueryOneCommandInvoker()));
    }

    private static CommandInvoker maybeProxyed(CommandInvoker commandInvoker) {
        return OrmConfigHolder.hasCommandInterceptor() ? CommandInvokerProxy.create(commandInvoker) : commandInvoker;
    }

    public static CommandInvoker getBy(CommandMethodMetadata methodMetadata) {

        String metaKey = methodMetadata.getMetaKey();
        if (cachedCommandInvokerMaps.containsKey(metaKey)) {
            return cachedCommandInvokerMaps.get(metaKey);
        }
        String cmdInvokerKey = parseMatchKey(methodMetadata);
        CommandInvoker commandDirectInvoker = commandInvokerMaps.get(cmdInvokerKey);
        if (commandDirectInvoker == null) {
            throw new HORMException("no suitable command invoker");
        }
        // 缓存起来，不再次经过计算
        cachedCommandInvokerMaps.put(metaKey, commandDirectInvoker);
        return commandDirectInvoker;
    }

    private static String parseMatchKey(CommandMethodMetadata methodMetadata) {

        Resource resource = methodMetadata.getResource();
        Class<?> returnType = methodMetadata.getReturnType();
        Class<?> truelyReturnType = methodMetadata.getTruelyReturnType();
        // 查询 ?
        if (isQuery(resource)) {
            StringBuilder sb = new StringBuilder();
            sb.append("QUERY.");
            // 列表 ?
            if (returnType == List.class) {
                sb.append("LIST.");
                // BEAN ?
                if (!isPrimaryOrMappedWrapTypeOrFrequentType(truelyReturnType)
                        && !isRecordForTruelyReturnType(truelyReturnType)) {
                    sb.append("BEAN");
                    return sb.toString();
                }
                // Record ?
                if (isRecordForTruelyReturnType(truelyReturnType)) {
                    sb.append("RECORD");
                    return sb.toString();
                }
                // Object ?
                if (isPrimaryOrMappedWrapTypeOrFrequentType(truelyReturnType)) {
                    sb.append("OBJ");
                    return sb.toString();
                }
            }
            // 分页 ?
            if (returnType == Page.class) {
                sb.append("PAGE.");
                // Record ?
                if (isRecordForTruelyReturnType(truelyReturnType)) {
                    sb.append("RECORD");
                    return sb.toString();
                }
                // Bean ?
                if (!isRecordForTruelyReturnType(truelyReturnType)) {
                    sb.append("BEAN");
                    return sb.toString();
                }
                // Object ? 暂时不支持
            }
            if (returnType != List.class && returnType != Page.class) {
                // 不是列表、不是分页
                sb.append("ONE.");
                // BEAN ?
                if (!isPrimaryOrMappedWrapTypeOrFrequentType(returnType) && !isRecordForTruelyReturnType(returnType)) {
                    sb.append("BEAN");
                    return sb.toString();
                }
                // Record ?
                if (isRecordForTruelyReturnType(returnType)) {
                    sb.append("RECORD");
                    return sb.toString();
                }
                // Object
                if (isPrimaryOrMappedWrapTypeOrFrequentType(returnType)) {
                    sb.append("OBJ");
                    return sb.toString();
                }
            }
        }
        if (isUpdate(resource)) {
            return "UPDATE.";
        }
        if (isExecute(resource)) {
            return "EXECUTE.";
        }
        if (isInsert(resource)) {
            return "INSERT.";
        }
        return "NONE";
    }

    /**
     * 是否是 cn.sylinx.hbatis.db.common.Record 类型
     * 
     * @return
     */
    private static boolean isRecordForTruelyReturnType(Class<?> truelyReturnType) {
        return truelyReturnType == Record.class;
    }

    /**
     * 是否是8种原始类型或者对应的封装类型或者是常用类型
     * 
     * @return
     */
    private static boolean isPrimaryOrMappedWrapTypeOrFrequentType(Class<?> truelyReturnType) {
        return primaryOrMappedWrapTypeOrFrequentType.contains(truelyReturnType);
    }

    // 是查询
    private static boolean isQuery(Resource resource) {
        return ResourceType.QUERY == resource.resourceType();
    }

    private static boolean isInsert(Resource resource) {
        return ResourceType.INSERT == resource.resourceType();
    }

    private static boolean isUpdate(Resource resource) {
        return ResourceType.UPDATE == resource.resourceType();
    }

    private static boolean isExecute(Resource resource) {
        return ResourceType.EXECUTE == resource.resourceType();
    }
}